if (Modernizr.canvas) {

   jpan.labyrinth || (function($) {

      var $jo = jpan.labyrinthObjects;
      var maze = new $jo.Labyrinth();
      var canvasId = "labyrinth";
      var canvas = null;
      var context = null;
      var debug_enabled = true;
      var currentCell;
      var mazeCellStrack = [];
      var $j = {

         init: function () {
            $j.initLog();
            canvas = document.getElementById(canvasId);
            context = canvas.getContext("2d");
            // resize canvas to maximum window size
            $j.initGameMetrics();
            $j.initEvents();
            $j.startGame();
         },
         /*********************************************************************
          * play game
          **********************************************************************/
         startGame: function(_params) {
            $j.render_labyrinth(_params);
            $j.draw_maze();
            $j.add_beast();
            $j.drawPlayer();
         },
         moveTo: function(moveTo) {
            if (moveTo instanceof $jo.Cell) {
               currentCell.visited = true;
               $j.draw_cell(currentCell);
               currentCell = moveTo;
               $j.drawPlayer();
            }
            else if (moveTo instanceof $jo.End) {
               alert("Joepie je bent eruit!!!");
            }
         },
         /*********************************************************************
          * render labyrinth
          **********************************************************************/
         render_labyrinth: function(_params) {
            var params = {
               renderAlgorithm : 'backtracker'
            }
            $.extend(params, _params);
            var startX = 1;
            var startY = Math.floor(Math.random() * maze.cellsY) + 1;
            var startCell = $j.create_cell({x:startX, y:startY});
            currentCell = startCell;
            startCell.w = new $jo.Start();
            maze.grid[startX - 1][startY - 1] = startCell;
            if (params.renderAlgorithm == 'backtracker') {
               $j.recursive_backtracker_maze(startCell);
            } else if (params.renderAlgorithm == 'randomizedPrim'){
               $j.randomized_prim_maze(startCell);
            } else {
               alert("can't render maze, unknown algorithm '"+params.renderAlgorithm+"'");
            }
            // select random end cell
            var endY = Math.floor(Math.random() * maze.cellsY) + 1;
            var lastCell = maze.grid[maze.cellsX - 1][endY - 1];
            lastCell.e = new $jo.End();
         },
         /*
          1: Mark the current cell as 'Visited'
          2: If the current cell has any neighbours which have not been visited
          2.1: Choose randomly one of the unvisited neighbours
          2.2: add the current cell to the stack
          2.3: remove the wall between the current cell and the chosen cell
          2.4: Make the chosen cell the current cell
          2.5: Recursively call this function
          3: else
          3.1: remove the last current cell from the stack
          3.2: Backtrack to the previous execution of this function
          */
         recursive_backtracker_maze: function(currentCell) {
            var validNeighbours = $j.get_empty_neighbours(currentCell);
            if (validNeighbours.length > 0) {
               // choose random neighbour
               var randomNeighbour = validNeighbours[Math.floor(Math.random() * validNeighbours.length)]
               // add current cell to stack
               mazeCellStrack.push(currentCell);
               // remove wall (first select which wall n,e,s,w
               $j.remove_wall_between_cells(currentCell, randomNeighbour);
               maze.grid[randomNeighbour.x - 1][randomNeighbour.y - 1] = randomNeighbour;
               // Make the chosen cell the current cell
               $j.recursive_backtracker_maze(randomNeighbour);
            }
            else {
               if (mazeCellStrack.length > 0) {
                  $j.recursive_backtracker_maze(mazeCellStrack.pop());
               }
            }
         },
         /*
          1: Start with a grid full of walls.
          2: Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
          3: While there are walls in the list:
          3.1: Pick a random wall from the list. If the cell on the opposite side isn't in the maze yet:
          3.1.1: Make the wall a passage and mark the cell on the opposite side as part of the maze.
          3.1.2: Add the neighboring walls of the cell to the wall list.
          */
         randomized_prim_maze: function(startCell) {
            // 2. mark as part of the maze
            startCell.partOfTheMaze = true;
            // 2. add walls of the cell to the wall list
            var wallList = [];
            wallList = $j.add_walls_to_list(wallList, startCell);
            // 3. While there are walls in the list
            var run = 1;
            var myMaxe = maze;
            while (wallList != undefined && wallList.length > 0) {
               // 3.1 pick a random wall
               var wallIndex = Math.floor(Math.random() * wallList.length);
               var randomWall = wallList[wallIndex]
               wallList.splice(wallIndex, 1);
               // if the cell on the opposite side isn't in the maze yet;
               var oppositeCell;
               var currentCell;
               if (randomWall.cell1.partOfTheMaze && !randomWall.cell2.partOfTheMaze) {
                  oppositeCell = randomWall.cell2;
                  currentCell = randomWall.cell1;
               } else if (!randomWall.cell1.partOfTheMaze && randomWall.cell2.partOfTheMaze) {
                  oppositeCell = randomWall.cell1;
                  currentCell = randomWall.cell2;
               } else {
                  $j.log("oops neither side of the wall is the current cell.");
               }
               var neighbourCell = maze.grid[oppositeCell.x-1][oppositeCell.y-1];
               // Isn't the cell on the opposite side in the maze yet:
               if (neighbourCell == undefined) {
                  neighbourCell = $j.create_cell({x:oppositeCell.x, y:oppositeCell.y});
                  // 3.1.1 Make the wall a passage
                  $j.remove_wall_between_cells(currentCell, neighbourCell);
                  // 3.1.1 mark the cell on the opposite side as part of the maze
                  neighbourCell.partOfTheMaze = true;
                  maze.grid[neighbourCell.x-1][neighbourCell.y-1] = neighbourCell;
                  // 3.1.2  Add the neighboring walls of the cell to the wall list.
                  wallList = $j.add_walls_to_list(wallList, neighbourCell);
               }
               run++;
            }
         },
         add_walls_to_list: function(wallList, cell) {
            wallList = $j.add_wall_to_list(wallList, cell.n);
            wallList = $j.add_wall_to_list(wallList, cell.e);
            wallList = $j.add_wall_to_list(wallList, cell.s);
            wallList = $j.add_wall_to_list(wallList, cell.w);
            return wallList;
         },
         add_wall_to_list: function(wallList, object) {
            if (wallList == undefined) {
               wallList = [];
            }
            if (object instanceof $jo.Wall) {
               wallList.push(object);
            }
            return wallList;
         },
         add_border_walls_cell: function(cell) {
            if (cell.x == 1) {
               cell.w = new $jo.BorderWall();
            }
            if (cell.x == maze.cellsX) {
               cell.e = new $jo.BorderWall();
            }
            if (cell.y == 1) {
               cell.n = new $jo.BorderWall();
            }
            if (cell.y == maze.cellsY) {
               cell.s = new $jo.BorderWall();
            }
         },
         create_cell: function(coordinates) {
            var aCell = new $jo.Cell(coordinates);
            // first init all to standard walls
            aCell.n = new $jo.Wall(aCell, new $jo.Cell({x:aCell.x, y:aCell.y - 1}));
            aCell.e = new $jo.Wall(aCell, new $jo.Cell({x:aCell.x + 1, y:aCell.y}));
            aCell.s = new $jo.Wall(aCell, new $jo.Cell({x:aCell.x, y:aCell.y + 1}));
            aCell.w = new $jo.Wall(aCell, new $jo.Cell({x:aCell.x - 1, y:aCell.y}));
            // detect if cell is at a border if so set a BorderWall
            $j.add_border_walls_cell(aCell);
            return aCell;
         },
         remove_wall: function(wall) {
            $j.remove_wall_between_cells(wall.cell1, wall.cell2);
         },
         remove_wall_between_cells: function(cell1, cell2) {
            if (cell1.x == cell2.x) {
               if (cell1.y > cell2.y) { // north
                  cell1.n = cell2;
                  cell2.s = cell1;
               } else { // south
                  cell1.s = cell2;
                  cell2.n = cell1;
               }
            }
            else {
               if (cell1.x > cell2.x) { // west
                  cell1.w = cell2;
                  cell2.e = cell1;
               } else { // east
                  cell1.e = cell2;
                  cell2.w = cell1;
               }
            }

         },
         get_empty_neighbours: function(cell) {
            var emptyNeighbours = [];
            // check north
            var neighbourCell = $j.create_cell({x:cell.x, y:cell.y - 1});
            if (cell.y > 1 && !$j.is_visited(neighbourCell)) {
               emptyNeighbours.push(neighbourCell);
            }
            // check east
            neighbourCell = $j.create_cell({x:cell.x + 1, y:cell.y});
            if (cell.x < maze.cellsX && !$j.is_visited(neighbourCell)) {
               emptyNeighbours.push(neighbourCell);
            }
            // check south
            neighbourCell = $j.create_cell({x:cell.x, y:cell.y + 1});
            if (cell.y < maze.cellsY && !$j.is_visited(neighbourCell)) {
               emptyNeighbours.push(neighbourCell);
            }
            // check west
            neighbourCell = $j.create_cell({x:cell.x - 1, y:cell.y});
            if (cell.x > 1 && !$j.is_visited(neighbourCell)) {
               emptyNeighbours.push(neighbourCell);
            }
            return emptyNeighbours;
         }
         ,
         is_visited: function(cell) {
            return maze.grid[cell.x - 1][cell.y - 1] != undefined;
         }
         ,
         /*********************************************************************
          * game draw logic
          **********************************************************************/
         drawPlayer: function() {
            context.fillStyle = maze.playerColor;
            context.beginPath();
            context.arc(
                  currentCell.x * maze.cellWidth - maze.cellWidth / 2,
                  currentCell.y * maze.cellHeight - maze.cellWidth / 2,
                  maze.cellWidth * 0.15,
                  0, Math.PI * 2, true);
            context.closePath();
            context.stroke();
            context.fill();
         },
         add_beast: function() {
            var beast = new $jo.Beast();
            beast.x = 1;
            beast.y = 1;
            context.fillStyle = maze.beastColor;
            context.beginPath();
            context.arc(
                  currentCell.x * maze.cellWidth - maze.cellWidth / 2,
                  currentCell.y * maze.cellHeight - maze.cellWidth / 2,
                  maze.cellWidth * 0.15,
                  0, Math.PI * 2, true);
            context.closePath();
            context.stroke();
            context.fill();

         },
         draw_maze: function() {
            context.fillStyle = maze.gameBackGroundColor;
            context.fillRect(0, 0, maze.gameWidth, maze.gameHeight);
            // draw a grid
            /*
             for (var x = 0.5; x < maze.gameWidth; x += (maze.gameWidth / maze.cellsX)) {
             context.moveTo(x, 0);
             context.lineTo(x, maze.gameHeight);
             }            //$j.context.
             for (var y = 0.5; y < maze.gameHeight; y += (maze.gameHeight / maze.cellsY)) {
             context.moveTo(0, y);
             context.lineTo(maze.gameWidth, y);
             }
             context.strokeStyle = maze.gridLineColor;
             context.strokeRect(0, 0, maze.gameWidth - 1, maze.gameHeight - 1);
             context.stroke();
             */
            for (i = 0; i < maze.grid.length; i++) {
               for (j = 0; j < maze.grid[i].length; j++) {
                  var aCell = maze.grid[i][j];
                  if (aCell)
                  {
                     $j.draw_cell(aCell);
                  }
                  else
                  {
                     context.fillStyle = $j.notValid;
                     // add a pixel to the x and y position and remove it from the width and height, cause we don't want to draw over the grid lines.
                     context.fillRect(
                           i * maze.cellWidth + 1,
                           j * maze.cellHeight + 1,
                           maze.cellWidth - 1,
                           maze.cellHeight - 1);
                  }
               }
            }
         }
         ,
         draw_cell:function(cell) {
            context.fillStyle = maze.floorColor;
            context.strokeStyle = maze.floorColor;
            // add a pixels to the x and y position and remove it from the width and height, cause we don't want to draw over the walls of the cell there drawn seperatly :-)
            $j.draw_floor(cell);
            context.strokeRect(
                  (cell.x - 1) * maze.cellWidth + 1 + maze.wallWidth,
                  (cell.y - 1) * maze.cellHeight + 1 + maze.wallWidth,
                  maze.cellWidth - 1 - 2 * maze.wallWidth,
                  maze.cellHeight - 1 - 2 * maze.wallWidth);
            // draw floors of the walls first
            context.fillStyle = maze.floorColor;
            if (cell.n instanceof $jo.Cell) $j.draw_north_wall(cell);
            if (cell.e instanceof $jo.Cell) $j.draw_east_wall(cell);
            if (cell.s instanceof $jo.Cell) $j.draw_south_wall(cell);
            if (cell.w instanceof $jo.Cell) $j.draw_west_wall(cell);
            // draw walls
            context.fillStyle = maze.wallColor;
            if (cell.n instanceof $jo.Wall) $j.draw_north_wall(cell);
            if (cell.e instanceof $jo.Wall) $j.draw_east_wall(cell);
            if (cell.s instanceof $jo.Wall) $j.draw_south_wall(cell);
            if (cell.w instanceof $jo.Wall) $j.draw_west_wall(cell);
            context.fillStyle = maze.borderWallColor;
            if (cell.n instanceof $jo.BorderWall) $j.draw_north_wall(cell);
            if (cell.e instanceof $jo.BorderWall) $j.draw_east_wall(cell);
            if (cell.s instanceof $jo.BorderWall) $j.draw_south_wall(cell);
            if (cell.w instanceof $jo.BorderWall) $j.draw_west_wall(cell);
            context.fillStyle = maze.startWallColor;
            if (cell.n instanceof $jo.Start) $j.draw_north_wall(cell, {drawFloor:true});
            if (cell.e instanceof $jo.Start) $j.draw_east_wall(cell, {drawFloor:true});
            if (cell.s instanceof $jo.Start) $j.draw_south_wall(cell, {drawFloor:true});
            if (cell.w instanceof $jo.Start) $j.draw_west_wall(cell, {drawFloor:true});
            context.fillStyle = maze.endWallColor;
            if (cell.n instanceof $jo.End) $j.draw_north_wall(cell, {drawFloor:true});
            if (cell.e instanceof $jo.End) $j.draw_east_wall(cell, {drawFloor:true});
            if (cell.s instanceof $jo.End) $j.draw_south_wall(cell, {drawFloor:true});
            if (cell.w instanceof $jo.End) $j.draw_west_wall(cell, {drawFloor:true});
            // draw visited
            if (cell.visited) {
               context.fillStyle = maze.visitedColor;
               context.beginPath();
               context.arc(
                     cell.x * maze.cellWidth - maze.cellWidth / 2,
                     cell.y * maze.cellHeight - maze.cellWidth / 2,
                     maze.cellWidth * 0.05,
                     0, Math.PI * 2, true);
               context.closePath();
               context.stroke();
               context.fill();
            }
         },
         draw_floor: function(cell) {
            context.fillRect(
                  (cell.x - 1) * maze.cellWidth + 1 + maze.wallWidth,
                  (cell.y - 1) * maze.cellHeight + 1 + maze.wallWidth,
                  maze.cellWidth - 1 - 2 * maze.wallWidth,
                  maze.cellHeight - 1 - 2 * maze.wallWidth);
         },
         draw_north_wall: function(cell, _params) {
            var params = {drawFloor:false};
            $.extend(params,_params);
            if (params.drawFloor) {
               $j.draw_floor(cell);
            }
            context.fillRect(
                  (cell.x - 1) * maze.cellWidth + 1,
                  (cell.y - 1) * maze.cellHeight + 1,
                  maze.cellWidth - 1,
                  maze.wallWidth);
         },
         draw_east_wall: function(cell, _params) {
            var params = {drawFloor:false};
            $.extend(params,_params);
            if (params.drawFloor) {
               $j.draw_floor(cell);
            }
            context.fillRect(
                  (cell.x) * maze.cellWidth - maze.wallWidth,
                  (cell.y - 1) * maze.cellHeight + 1,
                  maze.wallWidth,
                  maze.cellHeight - 1);
         },
         draw_south_wall: function(cell, _params) {
            var params = {drawFloor:false};
            $.extend(params,_params);
            if (params.drawFloor) {
               $j.draw_floor(cell);
            }
            context.fillRect(
                  (cell.x - 1) * maze.cellWidth + 1,
                  (cell.y) * maze.cellHeight - maze.wallWidth,
                  maze.cellWidth - 1,
                  maze.wallWidth);
         },
         draw_west_wall: function(cell, _params) {
            var params = {drawFloor:false};
            $.extend(params,_params);
            if (params.drawFloor) {
               $j.draw_floor(cell);
            }
            context.fillRect(
                  (cell.x - 1) * maze.cellWidth + 1,
                  (cell.y - 1) * maze.cellHeight + 1,
                  maze.wallWidth,
                  maze.cellHeight - 1);
         },
         clear_square:function(x, y) {
            context.fillStyle = $j.gameBackGroundColor;
            // add a pixel to the x and y position and remove it from the width and height, cause we don't want to draw over the grid lines.
            context.fillRect(
                  (x - 1) * maze.cellWidth + 1,
                  (y - 1) * maze.cellHeight + 1,
                  maze.cellWidth - 1,
                  maze.cellHeight - 1);
         }
         ,

         /*********************************************************************
          * game startup logic
          **********************************************************************/
         initGameMetrics:function() {
            if ($(window).height() / maze.gameSizeRatio < $(window).width()) {
               maze.gameHeight = $(window).height() * 0.95; // take of 5%
               maze.gameWidth = maze.gameHeight / maze.gameSizeRatio;
            }
            else {
               maze.gameWidth = $(window).width() * 0.95; // take of 5%
               maze.gameHeight = maze.gameWidth * maze.gameSizeRatio;
            }
            maze.initGrid();
            canvas.width = maze.gameWidth;
            canvas.height = maze.gameHeight;
            maze.cellWidth = maze.gameWidth / maze.cellsX;
            maze.cellHeight = maze.gameHeight / maze.cellsY;
         }
         ,
         initEvents: function() {
            $("#" + canvasId).click($j.clicked);
            $(window).keydown(function(keyEvent) {
               //      printKeyCode(keyEvent);
               $j.checkKeyDown(keyEvent);
            });
            $(".gameMenu .simple").click(function(event) {
               event.preventDefault();
               $j.initGameMetrics();
               $j.startGame({renderAlgorithm : 'backtracker'});
            });
            $(".gameMenu .hard").click(function(event) {
               event.preventDefault();
               $j.initGameMetrics();
               $j.startGame({renderAlgorithm : 'randomizedPrim'});
            });
            //canvas.addEventListener("click", $j.clicked, false);
         },
         checkKeyDown: function (keyEvent) {
            var keyCodePressed = keyEvent.keyCode ? keyEvent.keyCode : keyEvent.which;
            if (keyCodePressed == 38) {// north
               $j.moveTo(currentCell.n);
            }
            if (keyCodePressed == 39) {// east
               $j.moveTo(currentCell.e);
            }
            if (keyCodePressed == 40) {// sourth
               $j.moveTo(currentCell.s);
            }
            if (keyCodePressed == 37) {// west
               $j.moveTo(currentCell.w);
            }
         }
         ,
         clicked: function (event) {
            var canvasX = event.pageX - canvas.offsetLeft;
            var canvasY = event.pageY - canvas.offsetTop;
            $j.draw_square(parseInt(canvasX / maze.cellWidth) + 1, parseInt(canvasY / maze.cellHeight) + 1);
         }
         ,
         log: function() {/* default noop */
         }
         ,
         initLog: function() {
            if (debug_enabled && typeof window.console !== 'undefined') {
               $j.log = function(msg) {
                  window.console.debug(msg);
               };
            }
         }

      }; // jpan.labyrinth
      jpan.labyrinth = $j;


   })(jQuery);
} else {
   // no native canvas support available :(
   alert("sorry no HTML 5 support")
}